// File:       stdiobuf.c++
// Version:    1.00
// Author:     (c) Miles Sabin, 1997
// Purpose:    streambuf wrapper for C style FILEs

// Change log:
//  16/02/97   v. 1.00
//  30/03/97   Improved stdio synchronization.
//  17/05/98   Commented out debugging printf. (by Alexander Thoukydides)

#include "stdiobuf.h"

#include <stdio.h>
#include "ios.h"
#include "newcasts.h"


// Implementation of stdiobuf

class StdiobufSync
{
  public:

    StdiobufSync(stdiobuf* sb)
      : sb_(sb)
      { sb_->real_sync_stdio_with_stream(); }

    ~StdiobufSync()
      { sb_->real_sync_stream_with_stdio(); }

  private:

    stdiobuf* sb_;
};


stdiobuf::stdiobuf(FILE* file, streamsize buffer_size)
  : file_(file),
    buffer_(0),
    buffer_size_(buffer_size)
  {}

stdiobuf::~stdiobuf()
  { close(); }

bool stdiobuf::is_open() const
  { return file_ != 0; }

stdiobuf* stdiobuf::open(char const* s, ios::openmode mode, streamsize buffer_size)
  {
    if(is_open())
      return 0;

    buffer_size_ = buffer_size;

    char mode_string[4];
    char* mode_ptr = mode_string;

    if((mode&ios::in) != 0)
      *mode_ptr++ = 'r';

    if((mode&ios::out) != 0)
    {
      if((mode&ios::app) != 0)
        *mode_ptr++ = 'a';
      else if((mode&ios::trunc) != 0)
        *mode_ptr++ = 'w';
      else
        ++mode_ptr;

      if((mode&ios::in) != 0)
        *mode_ptr++ = '+';
    }

    if((mode&ios::binary) != 0)
      *mode_ptr++ = 'b';

    *mode_ptr = 0;

    if((file_ = fopen(s, mode_string)) == 0)
    {
      // printf("open failed %s %s\n", s, mode_string);
      return 0;
    }

    if((mode&ios::ate) != 0 && fseek(file_, 0, SEEK_END) != 0)
      return 0;

    return this;
  }

stdiobuf* stdiobuf::close()
  {
    if(!is_open())
      return 0;

    StdiobufSync syncbuf(this);

    bool fail = fclose(file_);
    file_ = 0;

    delete[] buffer_;
    buffer_ = 0;

    return fail ? 0 : this;
  }

int stdiobuf::showmanyc()
  {
    // requires is_open() == true

    if(file_->__base == 0)
      return 0;

    return (file_->__icnt >= 0 ? file_->__icnt : 0);
  }

streamsize stdiobuf::xsgetn(char* s, streamsize n)
  {
    // requires is_open() == true

    if(file_->__base == 0 && !create_buffer())
      return 0;

    StdiobufSync syncbuf(this);
    return fread(s, sizeof(char), n, file_);
  }

int stdiobuf::underflow()
  {
    // requires is_open() == true

    if(file_->__base == 0 && !create_buffer())
      return traits::eof();

    StdiobufSync syncbuf(this);

    char c = getc(file_);
    if(c != traits::eof())
    {
      --file_->__ptr;
      ++file_->__icnt;
    }

    return c;
  }

int stdiobuf::pbackfail(int c)
  {
    // requires is_open() == true

    if(file_->__base == 0 && !create_buffer())
      return traits::eof();

    StdiobufSync syncbuf(this);

    if(c != traits::eof())
      c = ungetc(c, file_);
    else if(fseek(file_, -1, SEEK_CUR) == 0)
      c = traits::not_eof(c);

    return c;
  }

streamsize stdiobuf::xsputn(char const* s, streamsize n)
  {
    // requires is_open() == true

    if(file_->__base == 0 && !create_buffer())
      return 0;

    StdiobufSync syncbuf(this);
    return fwrite(s, sizeof(char), n, file_);
  }

int stdiobuf::overflow(int c)
  {
    // requires is_open() == true

    if(file_->__base == 0 && !create_buffer())
      return traits::eof();

    StdiobufSync syncbuf(this);

    if(c != traits::eof())
      c = putc(c, file_);
    else if(fflush(file_) == 0)
      c = traits::not_eof(c);

    return c;
  }

basic_streambuf_char* stdiobuf::setbuf(char* s, streamsize n)
  {
    if(!is_open() || file_->__base != 0)
      return 0;

    int mode = (n == 0 ? _IONBF : _IOFBF);

    if(s == 0 && n == 0)
      n = 1;

    if(s == 0)
    {
      s = new char[n];
      buffer_ = s;
    }

    return (setvbuf(file_, s, mode, n) == 0 ? this : 0);
  }

int stdiobuf::seekoff(int off, ios::seekdir way, ios::openmode)
  {
    // requires is_open() == true

    StdiobufSync syncbuf(this);

    int whence = (way == ios::beg ? SEEK_SET : (way == ios::cur ? SEEK_CUR : SEEK_END));
    return (fseek(file_, off, whence) == 0 ? int(ftell(file_)) : -1);
  }

int stdiobuf::seekpos(int sp, ios::openmode)
  {
    // requires is_open() == true

    StdiobufSync syncbuf(this);
    return (fseek(file_, sp, SEEK_SET) == 0 ? sp : -1);
  }

int stdiobuf::sync()
  {
    StdiobufSync syncbuf(this);
    return (fflush(file_) == 0 ? 0 : -1);
  }

bool stdiobuf::create_buffer()
  { return setbuf(0, (buffer_size_ == -1 ? BUFSIZ : buffer_size_)) != 0; }

void stdiobuf::sync_stream_with_stdio()
  { real_sync_stream_with_stdio(); }

void stdiobuf::sync_stdio_with_stream()
  { real_sync_stdio_with_stream(); }

void stdiobuf::real_sync_stream_with_stdio()
  {
    if(file_->__base == 0)
      return;

    if(file_->__icnt > 0)
    {
      setg(reinterpret_cast(char*, file_->__base), reinterpret_cast(char*, file_->__ptr), reinterpret_cast(char*, file_->__ptr+file_->__icnt));
      setp(0, 0);
    }
    else if(file_->__ocnt > 0)
    {
      setg(0, 0, 0);
      setp(reinterpret_cast(char*, file_->__ptr), reinterpret_cast(char*, file_->__ptr+file_->__ocnt));
    }
    else
    {
      setg(0, 0, 0);
      setp(0, 0);
    }
  }

void stdiobuf::real_sync_stdio_with_stream()
  {
    if(file_->__base == 0)
      return;

    if(eback() != 0)
    {
      file_->__ptr = reinterpret_cast(unsigned char*, gptr());
      file_->__icnt = egptr()-gptr();
    }
    else if(pbase() != 0)
    {
      file_->__ptr = reinterpret_cast(unsigned char*, pptr());
      file_->__ocnt = epptr()-pptr();
    }
  }

